/*
 * Copyright (c) 2012 Jason Polites
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.defysoft.waukids;

import java.io.InputStream;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import android.content.Context;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;

import com.polites.android.GestureImageViewListener;

public class Custom_ImageView extends ImageView  {

   public static final String GLOBAL_NS = "http://schemas.android.com/apk/res/android";
   public static final String LOCAL_NS = "http://schemas.polites.com/android";

   private final Semaphore drawLock = new Semaphore(0);

   private Drawable drawable;

   private float x = 0, y = 0;

   private boolean layout = false;

   private float scaleAdjust = 1.0f;
   private float startingScale = -1.0f;

   private float scale = 1.0f;

   private float fitScaleHorizontal = 1.0f;
   private float fitScaleVertical = 1.0f;
   private float rotation = 0.0f;

   private float centerX;
   private float centerY;
   
   private Float startX, startY;

   private int hWidth;
   private int hHeight;

   private int resId = -1;
   private boolean recycle = false;
   private boolean strict = false;

   private int displayHeight;
   private int displayWidth;

   private int alpha = 255;
   private ColorFilter colorFilter;

   private int deviceOrientation = -1;
   private int imageOrientation;

   
   private GestureImageViewListener gestureImageViewListener;


   public Custom_ImageView(Context context, AttributeSet attrs, int defStyle) {
      this(context, attrs);
   }

   public Custom_ImageView(Context context, AttributeSet attrs) {
      super(context, attrs);
      
      String scaleType = attrs.getAttributeValue(GLOBAL_NS, "scaleType");
      
      if(scaleType == null || scaleType.trim().length() == 0) {
         setScaleType(ScaleType.CENTER_INSIDE);
      }
      
      String strStartX = attrs.getAttributeValue(LOCAL_NS, "start-x");
      String strStartY = attrs.getAttributeValue(LOCAL_NS, "start-y");
      
      if(strStartX != null && strStartX.trim().length() > 0) {
         startX = Float.parseFloat(strStartX);
      }
      
      if(strStartY != null && strStartY.trim().length() > 0) {
         startY = Float.parseFloat(strStartY);
      }
      
      setStartingScale(attrs.getAttributeFloatValue(LOCAL_NS, "start-scale", startingScale));

      setStrict(attrs.getAttributeBooleanValue(LOCAL_NS, "strict", strict));
      setRecycle(attrs.getAttributeBooleanValue(LOCAL_NS, "recycle", recycle));
      
      initImage();
   }

   public Custom_ImageView(Context context) {
      super(context);
      setScaleType(ScaleType.CENTER_INSIDE);
      initImage();
   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

      if(drawable != null) {
         int orientation = getResources().getConfiguration().orientation;
         if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
            displayHeight = MeasureSpec.getSize(heightMeasureSpec);

            if(getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
               float ratio = (float) getImageWidth() / (float) getImageHeight();
               displayWidth = Math.round( (float) displayHeight * ratio) ;
            }
            else {
               displayWidth = MeasureSpec.getSize(widthMeasureSpec);
            }
         }
         else {
            displayWidth = MeasureSpec.getSize(widthMeasureSpec);
            if(getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
               float ratio = (float) getImageHeight() / (float) getImageWidth();
               displayHeight = Math.round( (float) displayWidth * ratio) ;
            }
            else {
               displayHeight = MeasureSpec.getSize(heightMeasureSpec);
            }            
         }
      }
      else {
         displayHeight = MeasureSpec.getSize(heightMeasureSpec);
         displayWidth = MeasureSpec.getSize(widthMeasureSpec);
      }

      setMeasuredDimension(displayWidth, displayHeight);
   }

   @Override
   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
      super.onLayout(changed, left, top, right, bottom);
      if(changed || !layout) {
         setupCanvas(displayWidth, displayHeight, getResources().getConfiguration().orientation);
      }
   }

   protected void setupCanvas(int measuredWidth, int measuredHeight, int orientation) {

      if(deviceOrientation != orientation) {
         layout = false;
         deviceOrientation = orientation;
      }

      if(drawable != null && !layout) {
         int imageWidth = getImageWidth();
         int imageHeight = getImageHeight();

         hWidth = Math.round(((float)imageWidth / 2.0f));
         hHeight = Math.round(((float)imageHeight / 2.0f));
         
         measuredWidth -= (getPaddingLeft() + getPaddingRight());
         measuredHeight -= (getPaddingTop() + getPaddingBottom());
         
         computeCropScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
         
         if(startingScale <= 0.0f) {
            computeStartingScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
         }

         scaleAdjust = startingScale;

         this.centerX = (float) measuredWidth / 2.0f;
         this.centerY = (float) measuredHeight / 2.0f;
         
         if(startX == null) {
            x = centerX;
         }
         else {
            x = startX;
         }

         if(startY == null) {
            y = centerY;
         }
         else {
            y = startY;
         }   

   
         
         drawable.setBounds(-hWidth,-hHeight,hWidth,hHeight);


         layout = true;
      }
   }
   
   protected void computeCropScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
      fitScaleHorizontal = (float) measuredWidth / (float) imageWidth;
      fitScaleVertical = (float) measuredHeight / (float) imageHeight;
   }
   
   protected void computeStartingScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
      switch(getScaleType()) {
         case CENTER:
            // Center the image in the view, but perform no scaling.
            startingScale = 1.0f;
            break;
            
         case CENTER_CROP:
            // Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions
            // (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
            startingScale = Math.max((float) measuredHeight / (float) imageHeight, (float) measuredWidth/ (float) imageWidth);
            break;
            
         case CENTER_INSIDE:

            // Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions
            // (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).
            float wRatio = (float) imageWidth / (float) measuredWidth;
            float hRatio = (float) imageHeight / (float) measuredHeight;

            if(wRatio > hRatio) {
               startingScale = fitScaleHorizontal;
            }
            else {
               startingScale = fitScaleVertical;
            }

            break;
      }
   }

   protected boolean isRecycled() {
      if(drawable != null && drawable instanceof BitmapDrawable) {
         Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
         if(bitmap != null) {
            return bitmap.isRecycled();
         }
      }
      return false;
   }

   protected void recycle() {
      if(recycle && drawable != null && drawable instanceof BitmapDrawable) {
         Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
         if(bitmap != null) {
            bitmap.recycle();
         }
      }
   }
   
   @Override
   protected void onDraw(Canvas canvas) {
      if(layout) {
         if(drawable != null && !isRecycled()) {
            canvas.save();
            
            float adjustedScale = scale * scaleAdjust;

            canvas.translate(x, y);

            if(rotation != 0.0f) {
               canvas.rotate(rotation);
            }

            if(adjustedScale != 1.0f) {
               canvas.scale(adjustedScale, adjustedScale);
            }

            drawable.draw(canvas);

            canvas.restore();
         }

         if(drawLock.availablePermits() <= 0) {
            drawLock.release();
         }
      }
   }

   /**
    * Waits for a draw
    * @param max time to wait for draw (ms)
    * @throws InterruptedException
    */
   public boolean waitForDraw(long timeout) throws InterruptedException {
      return drawLock.tryAcquire(timeout, TimeUnit.MILLISECONDS);
   }

   

   protected void initImage() {
      if(this.drawable != null) {
         this.drawable.setAlpha(alpha);
         this.drawable.setFilterBitmap(true);
         if(colorFilter != null) {
            this.drawable.setColorFilter(colorFilter);
         }
      }
      
      if(!layout) {
         requestLayout();
         redraw();
      }
   }

   public void setImageBitmap(Bitmap image) {
      this.drawable = new BitmapDrawable(getResources(), image);
      initImage();
   }

   @Override
   public void setImageDrawable(Drawable drawable) {
      this.drawable = drawable;
      initImage();
   }

   public void setImageResource(int id) {
      if(this.drawable != null) {
         this.recycle();
      }
      if(id >= 0) {
         this.resId = id;
         setImageDrawable(getContext().getResources().getDrawable(id));
      }
   }

   public int getScaledWidth() {
      return Math.round(getImageWidth() * getScale());
   }
   
   public int getScaledHeight() {
      return Math.round(getImageHeight() * getScale());
   }
   
   public int getImageWidth() {
      if(drawable != null) {
         return drawable.getIntrinsicWidth();
      }
      return 0;
   }

   public int getImageHeight() {
      if(drawable != null) {
         return drawable.getIntrinsicHeight();
      }
      return 0;
   }

   public void moveBy(float x, float y) {
      this.x += x;
      this.y += y;
   }

   public void setPosition(float x, float y) {
      this.x = x;
      this.y = y;
   }

   public void redraw() {
      postInvalidate();
   }


   public void setScale(float scale) {
      scaleAdjust = scale;
   }

   public float getScale() {
      return scaleAdjust;
   }

   public float getImageX() {
      return x;
   }

   public float getImageY() {
      return y;
   }

   public boolean isStrict() {
      return strict;
   }

   public void setStrict(boolean strict) {
      this.strict = strict;
   }

   public boolean isRecycle() {
      return recycle;
   }

   public void setRecycle(boolean recycle) {
      this.recycle = recycle;
   }

   public void reset() {
      x = centerX;
      y = centerY;
      scaleAdjust = startingScale;
      
      redraw();
   }

   public void setRotation(float rotation) {
      this.rotation = rotation;
   }

   public void setGestureImageViewListener(GestureImageViewListener pinchImageViewListener) {
      this.gestureImageViewListener = pinchImageViewListener;
   }

   public GestureImageViewListener getGestureImageViewListener() {
      return gestureImageViewListener;
   }

   @Override
   public Drawable getDrawable() {
      return drawable;
   }

   @Override
   public void setAlpha(int alpha) {
      this.alpha = alpha;
      if(drawable != null) {
         drawable.setAlpha(alpha);
      }
   }

   @Override
   public void setColorFilter(ColorFilter cf) {
      this.colorFilter = cf;
      if(drawable != null) {
         drawable.setColorFilter(cf);
      }
   }

   @Override
   public void setImageURI(Uri mUri) {
      if ("content".equals(mUri.getScheme())) {
         try {
            String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION};
            
            Cursor cur = getContext().getContentResolver().query(mUri, orientationColumn, null, null, null);
            
            if (cur != null && cur.moveToFirst()) {
               imageOrientation = cur.getInt(cur.getColumnIndex(orientationColumn[0]));
            }  
            
            InputStream in = null;
            
            try {
               in = getContext().getContentResolver().openInputStream(mUri);
               Bitmap bmp = BitmapFactory.decodeStream(in);
               
               if(imageOrientation != 0) {
                  Matrix m = new Matrix();
                  m.postRotate(imageOrientation);
                  Bitmap rotated = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
                  bmp.recycle();
                  setImageDrawable(new BitmapDrawable(getResources(), rotated));
               }
               else {
                  setImageDrawable(new BitmapDrawable(getResources(), bmp));
               }
            }
            finally {
               if(in != null) {
                  in.close();
               }
               
               if(cur != null) {
                  cur.close();
               }
            }
         }
         catch (Exception e) {
            Log.w("GestureImageView", "Unable to open content: " + mUri, e);
         }
      }
      else {
         setImageDrawable(Drawable.createFromPath(mUri.toString()));
      }

      if (drawable == null) {
         Log.e("GestureImageView", "resolveUri failed on bad bitmap uri: " + mUri);
         // Don't try again.
         mUri = null;
      }
   }

   @Override
   public Matrix getImageMatrix() {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }      
      return super.getImageMatrix();
   }

   @Override
   public void setScaleType(ScaleType scaleType) {
      if(scaleType == ScaleType.CENTER ||
         scaleType == ScaleType.CENTER_CROP ||
         scaleType == ScaleType.CENTER_INSIDE) {
         
         super.setScaleType(scaleType);
      }
      else if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
   }

   @Override
   public void invalidateDrawable(Drawable dr) {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
      super.invalidateDrawable(dr);
   }

   @Override
   public int[] onCreateDrawableState(int extraSpace) {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
      return super.onCreateDrawableState(extraSpace);
   }

   @Override
   public void setAdjustViewBounds(boolean adjustViewBounds) {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
      super.setAdjustViewBounds(adjustViewBounds);
   }

   @Override
   public void setImageLevel(int level) {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
      super.setImageLevel(level);
   }

   @Override
   public void setImageMatrix(Matrix matrix) {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
   }

   @Override
   public void setImageState(int[] state, boolean merge) {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
   }

   @Override
   public void setSelected(boolean selected) {
      if(strict) {
         throw new UnsupportedOperationException("Not supported");
      }
      super.setSelected(selected);
   }


   
   public float getCenterX() {
      return centerX;
   }
   
   public float getCenterY() {
      return centerY;
   }
   
   public boolean isLandscape() {
      return getImageWidth() >= getImageHeight();
   }
   
   public boolean isPortrait() {
      return getImageWidth() <= getImageHeight();
   }
   
   public void setStartingScale(float startingScale) {
      this.startingScale = startingScale;
   }
   
   public void setStartingPosition(float x, float y) {
      this.startX = x;
      this.startY = y;
   }


   /**
    * Returns true if the image dimensions are aligned with the orientation of the device.
    * @return
    */
   public boolean isOrientationAligned() {
      if(deviceOrientation == Configuration.ORIENTATION_LANDSCAPE) {
         return isLandscape();
      }
      else if(deviceOrientation == Configuration.ORIENTATION_PORTRAIT) {
         return isPortrait();
      }
      return true;
   }
   
   public int getDeviceOrientation() {
      return deviceOrientation;
   }
}